package erfgame.core;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;

public enum Rotation {
	
	degrees0(0, 1, 1) {

		@Override
		public Dimension rotate(int width, int height, Dimension result) {
			result.width = width;
			result.height = height;
			return result;
		}

		@Override
		public PointTransformation getRotateInBoxTransformation(final int maxx, final int maxy) {
			return new PointTransformation() {
				public void transform(int x, int y, Point into) {
					into.x = x;
					into.y = y;
				}
			};
		}

		@Override
		public PointTransformation getRotationTransformation(Point aroundTimes2) {
			return new PointTransformation() {
				public void transform(int x, int y, Point into) {
					into.x = x;
					into.y = y;
				}
			};
		}

		@Override
		public CompassDirection rotate(CompassDirection direction) {
			return direction;
		}
	},
	degrees90(90, -1, 1){
		
		@Override
		public Dimension rotate(int width, int height, Dimension result) {
			result.width = height;
			result.height = width;
			return result;
		}

		
		@Override
		public PointTransformation getRotateInBoxTransformation(final int maxx, final int maxy) {
			return new PointTransformation() {
				public void transform(int x, int y, Point into) {
					into.x = maxx-y;
					into.y = x;
				}
			};
		}

		@Override
		public PointTransformation getRotationTransformation(final Point aroundTimes2) {
			return new PointTransformation() {
				public void transform(int x, int y, Point into) {
					int a = (x << 1) - aroundTimes2.x;
					int b = (y << 1) - aroundTimes2.y;
					into.x = (aroundTimes2.x - b) >> 1;
					into.y = (aroundTimes2.y + a) >> 1;
				}
			};
		}


		@Override
		public CompassDirection rotate(CompassDirection direction) {
			if( direction != null ) {
				switch( direction.ordinal() ) {
				case CompassDirection.ORDINAL_EAST:
					return CompassDirection.north;
				case CompassDirection.ORDINAL_NORTH:
					return CompassDirection.west;
				case CompassDirection.ORDINAL_SOUTH:
					return CompassDirection.east;
				case CompassDirection.ORDINAL_WEST:
					return CompassDirection.south;
				case CompassDirection.ORDINAL_NORTH_EAST:
					return CompassDirection.northWest;
				case CompassDirection.ORDINAL_NORTH_WEST:
					return CompassDirection.southWest;
				case CompassDirection.ORDINAL_SOUTH_EAST:
					return CompassDirection.northEast;
				case CompassDirection.ORDINAL_SOUTH_WEST:
					return CompassDirection.southEast;
				default:
					throw new RuntimeException("unrecognised direction : "+direction);
				}
			} else {
				return null;
			}
		}
	}, 
	degrees180(180, -1, -1){

		@Override
		public Dimension rotate(int width, int height, Dimension result) {
			result.width = width;
			result.height = height;
			return result;
		}

		@Override
		public PointTransformation getRotateInBoxTransformation(final int maxx, final int maxy) {
			return new PointTransformation() {
				public void transform(int x, int y, Point into) {
					into.x = maxx-x;
					into.y = maxy-y;
				}
			};
		}

		@Override
		public PointTransformation getRotationTransformation(final Point aroundTimes2) {
			return new PointTransformation() {
				public void transform(int x, int y, Point into) {
					int a = (x << 1) - aroundTimes2.x;
					int b = (y << 1) - aroundTimes2.y;
					into.x = (aroundTimes2.x - a) >> 1;
					into.y = (aroundTimes2.y - b) >> 1;
				}
			};
		}
		
		@Override
		public CompassDirection rotate(CompassDirection direction) {
			if( direction != null ) {
				switch( direction.ordinal() ) {
				case CompassDirection.ORDINAL_EAST:
					return CompassDirection.west;
				case CompassDirection.ORDINAL_NORTH:
					return CompassDirection.south;
				case CompassDirection.ORDINAL_SOUTH:
					return CompassDirection.north;
				case CompassDirection.ORDINAL_WEST:
					return CompassDirection.east;
				case CompassDirection.ORDINAL_NORTH_EAST:
					return CompassDirection.southWest;
				case CompassDirection.ORDINAL_NORTH_WEST:
					return CompassDirection.southEast;
				case CompassDirection.ORDINAL_SOUTH_EAST:
					return CompassDirection.northWest;
				case CompassDirection.ORDINAL_SOUTH_WEST:
					return CompassDirection.northEast;
				default:
					throw new RuntimeException("unrecognised direction : "+direction);
				}
			} else {
				return null;
			}
		}
	},
	
	degrees270(270, 1, -1){
		@Override
		public Dimension rotate(int width, int height, Dimension result) {
			result.width = height;
			result.height = width;
			return result;
		}
		
		@Override
		public PointTransformation getRotateInBoxTransformation(final int maxx, final int maxy) {
			return new PointTransformation() {
				public void transform(int x, int y, Point into) {
					into.x = y;
					into.y = maxy-x;
				}
			};
		}

		@Override
		public PointTransformation getRotationTransformation(final Point aroundTimes2) {
			return new PointTransformation() {
				public void transform(int x, int y, Point into) {
					int a = (x << 1) - aroundTimes2.x;
					int b = (y << 1) - aroundTimes2.y;
					into.x = (aroundTimes2.x + b) >> 1;
					into.y = (aroundTimes2.y - a) >> 1;
				}
			};
		}

		@Override
		public CompassDirection rotate(CompassDirection direction) {
			if( direction != null ) {
				switch( direction.ordinal() ) {
				case CompassDirection.ORDINAL_EAST:
					return CompassDirection.south;
				case CompassDirection.ORDINAL_NORTH:
					return CompassDirection.east;
				case CompassDirection.ORDINAL_SOUTH:
					return CompassDirection.west;
				case CompassDirection.ORDINAL_WEST:
					return CompassDirection.north;
				case CompassDirection.ORDINAL_NORTH_EAST:
					return CompassDirection.southEast;
				case CompassDirection.ORDINAL_NORTH_WEST:
					return CompassDirection.northEast;
				case CompassDirection.ORDINAL_SOUTH_EAST:
					return CompassDirection.southWest;
				case CompassDirection.ORDINAL_SOUTH_WEST:
					return CompassDirection.northWest;
				default:
					throw new RuntimeException("unrecognised direction : "+direction);
				}
			} else {
				return null;
			}
		}
	};

	public static final int ORDINAL_0 	= 0;
	public static final int ORDINAL_90 	= 1;
	public static final int ORDINAL_180 = 2;
	public static final int ORDINAL_270	= 3;

	private static final Point ZERO = new Point();

	private int degrees;
	private int xMult;
	private int yMult;
	
	Rotation(int degrees, int xMult, int yMult) {
		this.degrees = degrees;
		this.xMult = xMult;
		this.yMult = yMult;
	}
	
	public int getDegrees() {
		return this.degrees;
	}
	
	public int getXMult() {
		return this.xMult;
	}
	
	public int getYMult(){
		return this.yMult;
	}
	
	public Point rotate( int x, int y ) {
		return rotate( x, y, ZERO, new Point() );
	}
	
	public Point rotate( int x, int y, Point aroundTimes2 ) {
		return rotate( x, y, aroundTimes2, new Point() );
	}
	
	public Rotation getOpposite() {
		switch( ordinal() ) {
		case ORDINAL_0:
		default:
			return degrees0;
		case ORDINAL_90:
			return degrees270;
		case ORDINAL_180:
			return degrees180;
		case ORDINAL_270:
			return degrees90;
		}
	}
	
	public Point rotate(int x, int y, Point aroundTimes2, Point result) {
		getRotationTransformation( aroundTimes2 ).transform(x, y, result);
		return result;
	}
	
	public Point rotateInBox( int x, int y, int maxx, int maxy, Point result ) {
		getRotateInBoxTransformation( maxx, maxy ).transform(x, y, result);
		return result;
	}
	
	public void rotate(Rectangle r, int maxx, int maxy) {
		Rectangle3D r3d = new Rectangle3D(r);
		r3d.rotate(this, new Point(maxx, maxy), new Point());
		r.x = r3d.minX;
		r.y = r3d.minY;
		r.width = r3d.getWidth();
		r.height = r3d.getHeight();
	}
	
	public abstract CompassDirection rotate( CompassDirection direction );

	public abstract Dimension rotate(int width, int height, Dimension result);
	
	public abstract PointTransformation getRotationTransformation( Point aroundTimes2 );
	
	public abstract PointTransformation getRotateInBoxTransformation( int maxx, int maxy );
}
